@airma/react-state
Simple reducer-like
state-management with method action dispatch mode for react components.
Documents
Code first
Create reducer-like
function:
export function counting(state:number){
return {
count: `mount: ${state}`,
increase:()=>count + 1,
decrease:()=>count - 1,
add(...additions: number[]){
return additions.reduce((result, current)=>{
return result + current;
}, count);
}
};
}
Use reducer-like
function:
import {counting} from './model';
import {useModel} from '@airma/react-state';
......
const {count, increase, decrease, add} = useModel(counting, 0);
......
The reducer-like
function has a simple name model
. Use API model
can make it more simple.
Local state management
import {model} from '@airma/react-state';
const counting = model(function counting(state:number){
return {
count: `mount: ${state}`,
increase:()=>count + 1,
decrease:()=>count - 1,
add(...additions: number[]){
return additions.reduce((result, current)=>{
return result + current;
}, count);
}
};
});
......
const {count, increase, decrease, add} = counting.useModel(0);
......
Though, the basic function about model
is enhancing React.useReducer
to manage a local state, it also supports store usage with or without React.Context
to manage a global state.
React.Context state management
import {memo} from 'react';
import {model} from '@airma/react-state';
const countingStore = model(function counting(state:number){
return {
count: `mount: ${state}`,
increase:()=>count + 1,
decrease:()=>count - 1,
add(...additions: number[]){
return additions.reduce((result, current)=>{
return result + current;
}, count);
}
};
}).createStore(0);
......
const Increase = memo(()=>{
const increase = countingStore.useSelector(i => i.increase);
return <button onClick={increase}>+</button>;
});
const Count = memo(()=>{
const {count} = countingStore.useModel();
return <span>{count}</span>;
});
const Decrease = memo(()=>{
const decrease = countingStore.useSelector(i => i.decrease);
return <button onClick={decrease}>-</button>;
});
const Component = countingStore.provideTo(function Comp() {
return (
<div>
<Increase/>
<Count/>
<Decrease/>
</div>
);
});
......
Using model(xxx).createStore().asGlobal()
can build a global store.
Global state management
import {model} from '@airma/react-state';
const countingStore = model(function counting(state:number){
return {
count: `mount: ${state}`,
increase:()=>count + 1,
decrease:()=>count - 1,
add(...additions: number[]){
return additions.reduce((result, current)=>{
return result + current;
}, count);
}
};
}).createStore(0).asGlobal();
......
const Increase = memo(()=>{
const increase = countingStore.useSelector(i => i.increase);
return <button onClick={increase}>+</button>;
});
const Count = memo(()=>{
const {count} = countingStore.useModel();
return <span>{count}</span>;
});
const Decrease = memo(()=>{
const decrease = countingStore.useSelector(i => i.decrease);
return <button onClick={decrease}>-</button>;
});
const Component = function Comp() {
return (
<div>
<Increase/>
<Count/>
<Decrease/>
</div>
);
};
The useSelector
API is helpful for reducing render frequency, only when the selected result is changed, it make its owner component rerender.
A high performance usage about useSignal
In @airma/react-state@18.4.0
, a more simple and higher performance API useSignal
is provided.
import {model} from '@airma/react-state';
const counting = model(function countingModel(state:number){
return {
count: `mount: ${state}`,
increase:()=>count + 1,
decrease:()=>count - 1,
add(...additions: number[]){
return additions.reduce((result, current)=>{
return result + current;
}, count);
}
};
}).createStore().static();
......
const Increase = memo(()=>{
const signal = counting.useSignal();
return <button onClick={signal().increase}>+</button>;
});
const Count = memo(()=>{
const signal = counting.useSignal();
return <span>{signal().count}</span>;
});
const Decrease = memo(()=>{
const signal = counting.useSignal();
return <button onClick={signal().decrease}>-</button>;
});
const Component = function Comp({defaultCount}:{defaultCount:number}) {
counting.useSignal(defaultCount);
return (
<div>
<Increase/>
<Count/>
<Decrease/>
</div>
);
};
The useSignal
API is even better than API useSelector
, it computes out when to rerender component by the fields getting from instance automatically. And by using the signal
function, it always provides a newest instance in usage point, so it can avoid stale data and zombie-children problems more effectively.
Why support context store?
In @airma/react-state
, store is dynamic, every provider
copies a working instance for a context usage.
That means:
- The store data can be destroyed with its
provider
component unmount. - Components with same store provider can be used together in one parent component without state change effect to each other.
How to subscribe a grand parent provider store?
The store provider system in @airma/react-state
is designed with a tree structure. The nearest provider
finds store one-by-one from itself to its root parent provider
, and links the nearest matched provider
store to the subscriber useModel/useSelector
.
Does the state change of store leads a whole provider component rerender?
No, only the hooks subscribing this store
may rerender their owners. Every store change is notified to its subscriber like useModel
and useSelector
, and then the subscriber rerenders its owner by useState
.
Why not async action methods
Async action often makes the problem about stale data and zombie-children. So, a special tool to resolve this problem is necessary, you can try @airma/react-effect with it.
There are more examples, concepts and APIs in the documents of @airma/react-state
.
Browser Support
chrome: '>=91',
edge: '>=91',
firefox: '=>90',
safari: '>=15'